/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package server;

import com.SequData;
import chat.ChatCommand;
import chat.ChatMessage;
import chat.ChatProperties;
import com.ReliableDatagram;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

/**
 *
 * @author pedro
 */
public class UDPChatServer {

    private Integer myPort = null;
    private List<InetSocketAddress> serverList = new ArrayList<InetSocketAddress>();
    private ChatProperties props = new ChatProperties();
    private DatagramSocket mySocket = null;

    //diese members müssen synchronisiert werden
    private Map<String, ClientData> clientMap = new HashMap<String, ClientData>();
    private List<ChatMessage> messageQueue = new ArrayList<ChatMessage>();
    private int msgCounter = 0;
    private String nickWithLowestMsgId = null;
    private Map<InetSocketAddress, SequData> sequNrMap = new HashMap<InetSocketAddress, SequData>();
    
	//the lease map holds timer tasks for each client nickname
	//upon timer expiration the corresponding client will be removed from clientMap
	private Map<String, ClientTimer> timerMap = new HashMap<String, ClientTimer>();

    public static void main(String[] args) {
        (new UDPChatServer(args)).start();
    }

    protected UDPChatServer() {

    }

    protected UDPChatServer(String[] args) {
        final String ERROR_STRING = "Usage: UDPChatServer <port> <serverlistname1:port1> [serverlistname2:port2]...";
        if (args.length < 2) {
            System.err.println(ERROR_STRING);
            System.exit(1);
        } else {
            try {
                myPort = Integer.parseInt(args[0]);
            } catch (NumberFormatException ex) {
                System.err.println(ERROR_STRING);
                System.err.println("The parameter port must be numeric: " + args[0]);
                System.exit(1);
            }
            InetSocketAddress thisSocket = new InetSocketAddress("127.0.0.1", myPort);
            for (int i = 1; i < args.length; ++i) {
                StringTokenizer tokenizer = new StringTokenizer(args[i], ":");
                String listName = tokenizer.nextToken();
                Integer listPort = null;
                try {
                    listPort = Integer.parseInt(tokenizer.nextToken());
                } catch (NumberFormatException ex) {
                    System.err.println(ERROR_STRING);
                    System.err.println("The server list must contain peer servers in the format <name>:<port>, where port must be numeric: " + args[i]);
                    System.exit(1);
                }
                InetSocketAddress serverAddress = new InetSocketAddress(listName, listPort);
                if (serverAddress.equals(thisSocket)) continue;
                serverList.add(serverAddress);
                sequNrMap.put(serverAddress, new SequData(0, 0));
            }

            try {
                props.read();
            } catch (Exception e) {
                System.err.println("Unable to read properties!" + e.toString());
                System.exit(1);
            }
        }

    }

    private void start() {
        try {
            mySocket = new DatagramSocket(myPort);
            mySocket.setSoTimeout(0);
        } catch (IOException e) {
            System.err.println("Couldn't initialize socket: " + e.toString());
            System.exit(1);
        }
        while (true) {
            byte[] buf = new byte[0xFFFF];
            DatagramPacket packet = new DatagramPacket(buf, buf.length);
            try {
                mySocket.receive(packet);
            } catch (IOException ex) {
                System.err.println("Socket error: " + ex);
                continue;
            }
            (new ServerListener(this, packet, myPort, serverList, props.getTimeout())).start();
        }
    }

    public void sendCommand(ChatCommand chatCmd, InetSocketAddress recipient, int sequNr) {
        ReliableDatagram relDataSend = null;
        ByteArrayOutputStream bos = null;
        ObjectOutputStream out = null;
        byte[] buf = null;

        relDataSend = new ReliableDatagram();
        relDataSend.setSequenceNumber(sequNr);
        relDataSend.setData(chatCmd);
        relDataSend.setLoss(props.getLoss());
        bos = new ByteArrayOutputStream();
        try {
            out = new ObjectOutputStream(bos);
            out.writeObject(relDataSend);
            buf = bos.toByteArray();

            System.out.println("[sendCommand] " + recipient.getHostName() + ":" + recipient.getPort() + " sequNr " + sequNr + " cmd " + chatCmd.getCommand() +
                    " msgId " + chatCmd.getMsgId());
            DatagramPacket packet = new DatagramPacket(buf, buf.length, recipient);
            mySocket.send(packet);

            out.close();

        } catch (IOException e) {
            System.err.println("[sendCommand] exception: " + e);
        }
    }

	public void removeClient(String nickName) {
        System.err.println("[removeClient] " + nickName + " has been too long inactive");
		clientMap.remove(nickName);
		getTimerMap().remove(nickName);
	}

    /**
     * @return the clientMap
     */
    public Map<String, ClientData> getClientMap() {
        return clientMap;
    }

    /**
     * @return the messageQueue
     */
    public List<ChatMessage> getMessageQueue() {
        return messageQueue;
    }

    /**
     * @return the msgCounter
     */
    public int getMsgCounter() {
        return msgCounter;
    }

    /**
     * @return the nickWithLowestMsgId
     */
    public String getNickWithLowestMsgId() {
        return nickWithLowestMsgId;
    }

    /**
     * @param msgCounter the msgCounter to set
     */
    public void setMsgCounter(int msgCounter) {
        this.msgCounter = msgCounter;
    }

    /**
     * @param nickWithLowestMsgId the nickWithLowestMsgId to set
     */
    public void setNickWithLowestMsgId(String nickWithLowestMsgId) {
        this.nickWithLowestMsgId = nickWithLowestMsgId;
    }

    /**
     * @param messageQueue the messageQueue to set
     */
    public void setMessageQueue(List<ChatMessage> messageQueue) {
        this.messageQueue = messageQueue;
    }

    /**
     * @return the timerMap
     */
    public Map<String, ClientTimer> getTimerMap() {
        return timerMap;
    }

    /**
     * @return the sequNrMap
     */
    public Map<InetSocketAddress, SequData> getSequNrMap() {
        return sequNrMap;
    }
}
